package newcrunch.grader;

import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JEditorPane;
import javax.swing.JTextArea;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import newcrunch.gui.NewCrunchEditorPane;
import newcrunch.gui.RightTab;
import newcrunch.gui.multispantable.MultiSpanCellTable;
import newcrunch.gui.multispantable.TableCellListener;
import org.netbeans.swing.tabcontrol.TabData;
import org.netbeans.swing.tabcontrol.TabDataModel;

/**
 * Class for tracking whether a submission has been modified by the grader.
 * 
 * Controls the display of inline diff and whether the tab has been modified.
 * 
 * @author ccadm
 */
public class ModificationManager
{    
    private ArrayList<Tab> tabs = new ArrayList<Tab>();
    
    private Tab getTab(RightTab rightTab)
    {
        for (Tab tab : tabs)
            if (tab.rightTab == rightTab)
                return tab;
        throw new IllegalArgumentException("Tab not found.");
    }
    
    public String getOriginalText(RightTab rightTab)
    {
        return getTab(rightTab).originalText;
    }
    
    public boolean isTabModified(RightTab rightTab)
    {
        try {
            return getTab(rightTab).isModified;
        } catch (IllegalArgumentException e) {
            return false;
        }
    }
    
    public boolean hasModifiedTab()
    {
        for (Tab tab : tabs)
            if (tab.isModified)
                return true;
        
        return false;
    }
    
    public void setOriginalText(RightTab rightTab, String text)
    {
        getTab(rightTab).originalText = text;
    }
    
    public void setOriginalComment(RightTab rightTab, String text)
    {
        getTab(rightTab).originalComments = text;
    }
    
    public void setOldText(RightTab rightTab, String text)
    {
        getTab(rightTab).oldText = text;
    }
    
    public String getOldText(RightTab rightTab)
    {
        try {
            return getTab(rightTab).oldText;
        } catch (IllegalArgumentException exp) {
            return null;
        }
    }
    
    public void refresh(RightTab rightTab)
    {
        refreshDiff(rightTab);
        Tab tab = getTab(rightTab);
        tab.isModified = tab.originalComments != null && !rightTab.getCommentsField().getText().equals(tab.originalComments);
        tab.isModified |= tab.originalText != null && !rightTab.getEditor().getText().equals(tab.originalText);
        updateTabTitle(tab, tab.tabData, tab.model);
    }
    
    public void refreshDiff(RightTab rightTab)
    {
        String oldText = getTab(rightTab).oldText;
        NewCrunchEditorPane pane = rightTab.getEditor();
        pane.regenerateDiffArr(oldText, pane.getText());
    }
    
    private boolean isModified(Tab tab)
    {
        JTextArea comments = tab.rightTab.getCommentsField();
        NewCrunchEditorPane editor = tab.rightTab.getEditor();
        
        return (tab.originalComments != null && !comments.getText().equals(tab.originalComments)) ||
                (tab.originalText != null && !editor.getText().equals(tab.originalText)) || tab.isMarkingSchemeChanged;
    }
    
    public void trackTab(final RightTab rightTab, final TabData data, final TabDataModel model)
    {
        final Tab tab = new Tab(rightTab);
        
        tabs.add(tab);
        
        final NewCrunchEditorPane editor = rightTab.getEditor();
        final JTextArea comments = rightTab.getCommentsField();
        
        tab.originalText = editor.getText();
        tab.oldText = editor.getText();
        tab.originalComments = comments.getText();
        tab.model = model;
        tab.tabData = data;
        
        //track changes in comments and editor        
        comments.addCaretListener(new CaretListener(){
            @Override
            public void caretUpdate(CaretEvent e) {
                tab.isModified = isModified(tab);
                updateTabTitle(tab, data, model);
            }
        });
        editor.addCaretListener(new CaretListener(){
            @Override
            public void caretUpdate(CaretEvent e) {
                tab.isModified = isModified(tab);
                updateTabTitle(tab, data, model);
            }
        });
        editor.getDocument().addDocumentListener(
            new DocumentListener() {
                @Override
                public void insertUpdate(DocumentEvent e) {
                    editor.regenerateDiffArr(tab.oldText, editor.getText());
                }

                @Override
                public void removeUpdate(DocumentEvent e) {
                    editor.regenerateDiffArr(tab.oldText, editor.getText());
                }

                @Override
                public void changedUpdate(DocumentEvent e) {
                    editor.regenerateDiffArr(tab.oldText, editor.getText());
                }
        });        
        
        //track changes in the marking scheme
        MultiSpanCellTable markingSchemeTable = rightTab.getMarkingSchemeTable();
        Action action = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                tab.isMarkingSchemeChanged = true;
                tab.isModified = true;
                updateTabTitle(tab, data, model);
            }
        };
        markingSchemeTable.addPropertyChangeListener(new TableCellListener(markingSchemeTable, action));
        
        //listen for reset commands and original text commands
        rightTab.addPropertyChangeListener(new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                String propName = evt.getPropertyName();
                if ("reset".equals(propName))
                    reset(tab);
                
                else if ("setOldText".equals(propName))
                    tab.oldText = (String)evt.getNewValue();
                
                else if ("setOriginalText".equals(propName))
                    tab.originalText = (String)evt.getNewValue();
            }
        });
    }
    
    private void reset(Tab tab)
    {
        JEditorPane editor = ((RightTab)tab.tabData.getComponent()).getEditor();
        JTextArea comments = tab.rightTab.getCommentsField();
        
        tab.isModified = false;
        tab.isMarkingSchemeChanged = false;
        tab.originalText = editor.getText();
        tab.originalComments = comments.getText();
        
        updateTabTitle(tab, tab.tabData, tab.model);
    }
    
    private void updateTabTitle(Tab tab, TabData tabData, TabDataModel model)
    {
            int index = model.indexOf(tabData);
            String currTitle = model.getTab(index).getText();

            if (tab.isModified && !tab.isTitleChanged)
            {
                    model.setText(index, "*" + currTitle);
                    tab.isTitleChanged = true;
            }

            else if (!tab.isModified && tab.isTitleChanged)
            {
                model.setText(index, currTitle.substring(1));
                tab.isTitleChanged = false;
            }
    }
    
    //private class to hold all information pertaining to the tab being tracked
    private class Tab
    {
        public TabData tabData;
        public RightTab rightTab;
        public TabDataModel model;
        
        public boolean isModified;
        public boolean isTitleChanged;
        public boolean isMarkingSchemeChanged;
                
        public String oldText; //what the student submitted - for diff display
        public String originalText; //the code when it was first downloaded - for tab display
        public String originalComments;
        
        public Tab(RightTab rightTab)
        {
            this.rightTab = rightTab;
        }
    }
}
